/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.common.base.service.impl;

import com.google.common.base.Strings;
import com.je.common.base.DynaBean;
import com.je.common.base.exception.PlatformException;
import com.je.common.base.exception.PlatformExceptionEnum;
import com.je.common.base.log.CheckSlowSql;
import com.je.common.base.mapper.MetaMapper;
import com.je.common.base.service.MetaService;
import com.je.common.base.service.rpc.BeanService;
import com.je.common.base.util.StringUtil;
import com.je.ibatis.base.DBDynaBean;
import com.je.ibatis.extension.builder.MetaStatementBuilder;
import com.je.ibatis.extension.cache.MetaDataCacheManager;
import com.je.ibatis.extension.conditions.ConditionsWrapper;
import com.je.ibatis.extension.enums.DbType;
import com.je.ibatis.extension.metadata.model.Column;
import com.je.ibatis.extension.metadata.model.Table;
import com.je.ibatis.extension.plugins.pagination.Page;
import com.je.ibatis.extension.toolkit.Constants;
import com.je.ibatis.session.CustomConfiguration;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * MetaServiceImpl
 *
 * @author wangmm@ketr.com.cn
 * @date 2019/12/4
 */
@Service
public class MetaServiceImpl implements MetaService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    final BeanCopier beanCopier = BeanCopier.create(DBDynaBean.class, DynaBean.class, false);

    private final DataSource dataSource;

    private final MetaMapper baseDataMapper;

    private final SqlSessionFactory sessionFactory;

    private MetaStatementBuilder metaStatementBuilder;

    @Autowired
    public MetaServiceImpl(DataSource dataSource, MetaMapper baseDataMapper, SqlSessionFactory sessionFactory) {
        this.dataSource = dataSource;
        this.baseDataMapper = baseDataMapper;
        this.sessionFactory = sessionFactory;
        CustomConfiguration customConfiguration = (CustomConfiguration) sessionFactory.getConfiguration();
        metaStatementBuilder = customConfiguration.getMetaStatementBuilder();
    }

    private void setTableCode(DynaBean dynaBean, String tableCode) {
        if (dynaBean != null && Strings.isNullOrEmpty(dynaBean.getStr(Constants.KEY_TABLE_CODE))) {
            dynaBean.set(BeanService.KEY_TABLE_CODE, tableCode);
        }
    }

    private void setTableCode(Map<String, Object> map, String tableCode) {
        if (map != null && (!map.containsKey(Constants.KEY_TABLE_CODE) || Strings.isNullOrEmpty(map.get(Constants.KEY_TABLE_CODE).toString()))) {
            map.put(BeanService.KEY_TABLE_CODE, tableCode);
        }
    }

    @Override
    public Connection getConnection() {

        try {
            Connection connection;
            //循环获取，避免拿到已经失效的连接
            do {
                connection = dataSource.getConnection();
            } while (connection.isClosed());
            return connection;
        } catch (SQLException e) {
            throw new PlatformException("获取数据库连接失败！", PlatformExceptionEnum.CONNECTION_ERROR, e);
        }
    }

    @Override
    public DbType getDbType() {
        Connection connection = getConnection();
        try {
            DbType dbtype = DbType.getDbTypeByUrl(connection.getMetaData().getURL());
            connection.close();
            return dbtype;
        } catch (SQLException e) {
            throw new PlatformException("获取数据库连接失败！", PlatformExceptionEnum.CONNECTION_ERROR, e);
        }
    }

    @Override
    public void clearMyBatisCache() {
        MetaStatementBuilder metaBuilder = getMetaBuilder();
        if (metaBuilder != null) {
            MetaDataCacheManager cacheManager = metaBuilder.getCacheManager();
            cacheManager.clear();
        }
    }

    @Override
    public void clearMyBatisTableCache(String tableCode) {
        MetaStatementBuilder metaBuilder = getMetaBuilder();
        if (metaBuilder != null) {
            metaBuilder.builderTable(tableCode);
        }
    }

    @Override
    public void clearMyBatisFuncCache() {
        MetaStatementBuilder metaBuilder = getMetaBuilder();
        if (metaBuilder != null) {
            MetaDataCacheManager cacheManager = metaBuilder.getCacheManager();
            cacheManager.clearFunction();
        }
    }

    @Override
    public void clearMyBatisFuncCache(String funcCode) {
        MetaStatementBuilder metaBuilder = getMetaBuilder();
        if (metaBuilder != null) {
            metaBuilder.builderFunction(funcCode);
        }
    }

    /**
     * 获取元数据缓存
     */
    private MetaStatementBuilder getMetaBuilder() {
        Configuration configuration = sessionFactory.getConfiguration();
        if (configuration instanceof CustomConfiguration) {
            CustomConfiguration customConfiguration = (CustomConfiguration) configuration;
            return customConfiguration.getMetaStatementBuilder();
        }
        return null;
    }

    @Override
    public int insert(String tableCode, DynaBean beanMap) {
        if (StringUtils.isNotBlank(tableCode)) {
            beanMap.put(Constants.KEY_TABLE_CODE, tableCode);
        }

        if (beanMap.containsKey(beanMap.getTenantIdField()) && Strings.isNullOrEmpty(beanMap.getStr(beanMap.getTenantNameField()))) {
            beanMap.remove(beanMap.getTenantIdField());
            beanMap.remove(beanMap.getTenantNameField());
        }
        return baseDataMapper.insertMap(beanMap.fetchAllValues());
    }

    @Override
    public DynaBean insertAndReturn(DynaBean beanMap) {
        String tableCode = beanMap.getTableCode();
        if (beanMap.containsKey(beanMap.getTenantIdField()) && Strings.isNullOrEmpty(beanMap.getStr(beanMap.getTenantNameField()))) {
            beanMap.remove(beanMap.getTenantIdField());
            beanMap.remove(beanMap.getTenantNameField());
        }
        baseDataMapper.insertMap(beanMap.fetchAllValues());
        beanMap.put(Constants.KEY_TABLE_CODE, tableCode);
        return beanMap;
    }

    @Override
    public int insertBatch(String tableCode, List<DynaBean> list) {

        // 校验插入数据
        if (list == null || list.isEmpty()) {
            logger.info("insertBatch list is null or empty!");
            return 0;
        }

        //如果未指定表名从list中获取
        if (StringUtils.isBlank(tableCode)) {
            tableCode = list.get(0).getStr(Constants.KEY_TABLE_CODE);
        }

        //list转换并插入
        List<Map<String, Object>> collect = list.stream().map(p -> {
            if (p.containsKey(p.getTenantIdField()) && Strings.isNullOrEmpty(p.getStr(p.getTenantNameField()))) {
                p.remove(p.getTenantIdField());
                p.remove(p.getTenantNameField());
            }
            return (Map<String, Object>) p.fetchAllValues();
        }).collect(Collectors.toList());
        return baseDataMapper.insertBatch(tableCode, collect);
    }

    @Override
    public int update(String tableCode, String pkValue, DynaBean beanMap, ConditionsWrapper wrapper) {
        if (StringUtils.isNotBlank(tableCode)) {
            beanMap.put(Constants.KEY_TABLE_CODE, tableCode);
        }
        if (StringUtils.isNotBlank(pkValue)) {
            beanMap.put(Constants.KEY_PK_VALUE, pkValue);
        }

        return baseDataMapper.updateMap(beanMap.fetchAllValues(), wrapper);
    }

    @Override
    public DynaBean updateAndReturn(DynaBean beanMap) {
        String tableCode = beanMap.getTableCode();
        update(beanMap.getTableCode(), beanMap.getPkValue(), beanMap, null);
        beanMap.put(Constants.KEY_TABLE_CODE, tableCode);
        return beanMap;
    }

    @Override
    public int delete(String tableCode, ConditionsWrapper wrapper) {
        if (StringUtils.isNotBlank(tableCode)) {
            wrapper.table(tableCode);
        }
        return baseDataMapper.delete(wrapper);
    }

    @Override
    @CheckSlowSql
    public List<Map<String, Object>> selectSql(Page page, ConditionsWrapper wrapper) {
        List<Map<String, Object>> list = baseDataMapper.selectSql(page, wrapper);
        list = list == null ? new ArrayList<>() : list.stream().filter(Objects::nonNull).collect(Collectors.toList());
        list.forEach(each -> {
            setTableCode(each, wrapper.getTable());
        });
        return list;
    }

    @Override
    @CheckSlowSql
    public List<DynaBean> selectBeanSql(String tableCode, String sql) {
        return selectBeanSql(tableCode, sql, null);
    }

    @Override
    @CheckSlowSql
    public List<DynaBean> selectBeanSql(String tableCode, String sql, Object... params) {
        List<Map<String, Object>> list = selectSql(new Page<>(-1, -1), ConditionsWrapper.builder().table(tableCode).apply(sql, params));
        return mapToDynaBean(list, tableCode);
    }

    @Override
    public DynaBean selectOneBeanSql(String tableCode, String sql) {
        return selectOneBeanSql(tableCode, sql, null);
    }

    @Override
    @CheckSlowSql
    public DynaBean selectOneBeanSql(String tableCode, String sql, Object... params) {
        List<Map<String, Object>> list = selectSql(new Page<>(-1, 1), ConditionsWrapper.builder().table(tableCode).apply(sql, params));
        if (list.size() == 0) {
            return null;
        }
        return mapToDynaBean(list, tableCode).get(0);
    }

    private List<DynaBean> mapToDynaBean(List<Map<String, Object>> list, String tableCode) {
        List<DynaBean> dynaBeanList = new ArrayList<>();
        list.forEach(each -> {
            DynaBean dynaBean = new DynaBean(tableCode, true);
            dynaBean.setValues((HashMap<String, Object>) each);
            dynaBeanList.add(dynaBean);
        });
        return dynaBeanList;
    }

    @Override
    @CheckSlowSql
    public List<DynaBean> select(String tableCode, Page page, ConditionsWrapper wrapper, String columns) {
        if (wrapper == null) {
            wrapper = ConditionsWrapper.builder();
        }
        if (StringUtils.isNotBlank(tableCode)) {
            wrapper.table(tableCode);
        }

        String selectColumns = wrapper.getSelectColumns();
        if (!StringUtil.isNotEmpty(selectColumns)) {
            wrapper.getParameter().put(Constants.KEY_SELECT_COLUMN, columns);
        }
        List<Map<String, Object>> mapList = baseDataMapper.select(page, wrapper);
        if (Strings.isNullOrEmpty(tableCode)) {
            tableCode = wrapper.getTable();
        }
        List<DynaBean> list = transformDynaBean(mapList, tableCode);
        return list;
    }

    private String getTablePkCodeByTableCode(String tableCode) {
        Table table = metaStatementBuilder.table(tableCode);
        String pkCode = "";
        if (table.getId() != null) {
            pkCode = table.getId().getCode();
        }
        return pkCode;
    }


    private DynaBean converterOneDBBeanToJeBean(DBDynaBean dbDynaBean) {
        DynaBean dynaBean = new DynaBean();
        beanCopier.copy(dbDynaBean, dynaBean, null);
        return dynaBean;
    }

    @Override
    public DynaBean selectOne(String tableCode, ConditionsWrapper wrapper, String columns) {
        List<DynaBean> list = select(tableCode, 0, -1, wrapper);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() == 0) {
            return null;
        } else {
            logger.error("selectOne 查询出({})条数据。", list.size());
            throw new PlatformException("selectOne 查询出多条数据。", PlatformExceptionEnum.UNKOWN_ERROR);
        }
    }

    @Override
    @CheckSlowSql
    public List<Map<String, Object>> load(String funcCode, Page page, ConditionsWrapper wrapper) {
        if (wrapper == null) {
            wrapper = ConditionsWrapper.builder();
        }
        if (StringUtils.isNotBlank(funcCode)) {
            wrapper.function(funcCode);
        }
        List<Map<String, Object>> load = baseDataMapper.load(page, wrapper);
        page.setRecords(load == null ? new ArrayList<>() : load.stream().filter(Objects::nonNull).collect(Collectors.toList()));
        return load;
    }

    @Override
    public DynaBean selectOneByPk(String tableCode, String pkValue, String columns) {
        Map<String, Object> map = baseDataMapper.selectOneByPk(tableCode, pkValue, columns);
        if (map == null) {
            return null;
        }
        Table table = metaStatementBuilder.table(tableCode);
        return transformDynaBean(map, tableCode);
    }


    private DynaBean transformDynaBean(Map<String, Object> map, String tableCode) {
        DynaBean dynaBean = new DynaBean();
        dynaBean.setValues(map);
        dynaBean.setStr(BeanService.KEY_PK_CODE, getTablePkCodeByTableCode(tableCode));
        dynaBean.setStr(BeanService.KEY_TABLE_CODE, tableCode);
        return dynaBean;
    }

    private List<DynaBean> transformDynaBean(List<Map<String, Object>> select, String tableCode) {
        List<DynaBean> list = new ArrayList<>();
        for (Map<String, Object> map : select) {
            list.add(transformDynaBean(map, tableCode));
        }
        return list;
    }


    @Override
    public int executeSql(ConditionsWrapper wrapper) {
        String formatSql = wrapper.getSql().trim().toLowerCase();
        if (formatSql.startsWith("update") || formatSql.startsWith("create") || formatSql.startsWith("comment")
                || formatSql.startsWith("alter") || formatSql.startsWith("drop") || formatSql.startsWith("if")
                || formatSql.startsWith("exec") || formatSql.startsWith("execute")
                || formatSql.startsWith("truncate")) {
            return baseDataMapper.updateSql(wrapper);
        } else if (formatSql.startsWith("insert")) {
            return baseDataMapper.insertSql(wrapper);
        } else if (formatSql.startsWith("delete")) {
            return baseDataMapper.deleteSql(wrapper);
        } else {
            logger.error("语句不合法 ({})", wrapper.getSql());
            throw new PlatformException("语句不合法 :" + wrapper.getSql(), PlatformExceptionEnum.UNKOWN_ERROR);
        }
    }

    @Override
    @CheckSlowSql
    public long countBySql(ConditionsWrapper wrapper) {
        //支持不完整语句 ConditionsWrapper.builder().table("JE_CORE_TABLECOLUMN").apply("TABLECOLUMN_RESOURCETABLE_ID={0} AND TABLECOLUMN_CODE='SY__POSTIL'", table.getStr("JE_CORE_RESOURCETABLE_ID"))
        List<Map<String, Object>> list = selectSql(wrapper);
        if (list != null) {
            return Long.parseLong(String.valueOf(list.size()));
        }
        logger.error("语句无执行结果 {}，{}", wrapper.getSql(), wrapper.getParameter());
        return 0L;
    }

    /**
     * 获取所有列
     *
     * @param tableCode 表名
     * @return 列信息
     */
    private List<Column> getColumns(String tableCode) {
        //从 je-ibatis 中读取表缓存
        MetaStatementBuilder metaBuilder = getMetaBuilder();
        if (metaBuilder == null) {
            return null;
        }
        Table table = getMetaBuilder().getCacheManager().getTable(tableCode);
        if (table == null) {
            return null;
        }
        return table.getColumnList();
    }


//    private DynaBean transformDynaBean(Map<String, Object> model, String tableCode) {
//        return transformDynaBean(model, tableCode, getColumns(tableCode));
//    }

    /**
     * @param model     mybatis查询出的数据
     * @param tableCode 资源表名
     * @param columns   资源表所有列
     * @return DynaBean
     */
    private DynaBean transformDynaBean(Map<String, Object> model, String tableCode, List<Column> columns) {
        DynaBean dynaBean = new DynaBean();
        setTableCode(dynaBean, tableCode);
        dynaBean.setValues((HashMap<String, Object>) model);
        dynaBean.table(tableCode);
        if (columns != null) {
            columns.forEach(column -> {
                String columnCode = column.getCode();
                Object value = model.get(columnCode);
                if (ArrayUtils.contains(new String[]{"CLOB", "BIGCLOB"}, column.getType()) && value == null) {
                    if (model.get(columnCode) == null) {
                        dynaBean.set(columnCode, "");
                    }
                } else if (ArrayUtils.contains(new String[]{"NUMBER", "FLOAT"}, column.getType()) && value instanceof BigDecimal) {
                    if ("NUMBER".equals(column.getType())) {
                        if (value == null || Strings.isNullOrEmpty(value.toString())) {
                            dynaBean.set(columnCode, "0");
                        } else {
                            dynaBean.set(columnCode, Integer.parseInt(value.toString()));
                        }

                    } else {
                        if (value == null || Strings.isNullOrEmpty(value.toString())) {
                            dynaBean.set(columnCode, "0");
                        } else {
                            dynaBean.set(columnCode, Double.parseDouble(value.toString()));
                        }
                    }
                }
            });
        }
        return dynaBean;
    }

    private List<DynaBean> converterListDBBeanToJeBean(List<DBDynaBean> list) {
        List<DynaBean> result = new ArrayList<>();
        for (DBDynaBean dbDynaBean : list) {
            DynaBean dynaBean = converterOneDBBeanToJeBean(dbDynaBean);
            result.add(dynaBean);
        }
        return result;
    }

}